
#include "game_engine.h"
#include "utility.h"
#include "timer.h"
#include "SDL_gfx/SDL_framerate.h"
#include "particle.h"
#include "sprite.h"
#include "sprite_sheet.h"
#include "dude.h"
#include "battle_scene.h"
#include <sstream>
#include "SDL_mixer.h"
#include "button.h"
#include "picker_tray.h"

//Default window name
std::string winName = "PixelDudes 0.1";
//The singleton instance variable
GameEngine * gameEngineInstance = NULL;

#define NUMBER_AUDIO_CHANNELS 16

/* Main engine loop */
int GameEngine::Run()
{
    int initError = Init();
    if (initError != 0)
        return initError;

	RenderTarget* renderList = new RenderTarget("renderList");    
        
	ParticleSystem* pSys = NULL;
    ParticleSystem* pSys2 = NULL;
	//SpriteSheet* robotAnimated = NULL;
    
	/*
    pSys = new ParticleSystem("robot_fire");
	pSys->setPosition(0, 0);
    
#ifdef PARTICLE_1
	///*
	//For justin's spark particles based fire
	pSys->xrange = 15.0; //50.0;
	pSys->yrange = 15.0; //40.0;
	pSys->velx = 0.0;
	pSys->vely = -50.0; //0.0;
	pSys->velxrange = 20.0; //400.0;
	pSys->velyrange = 30.0; //400.0;
	pSys->gravityConstant = true; //false;
	pSys->life = 1.0;
	pSys->liferange = 0.5; //0.5;
	pSys->maxParticles = 15; //300;
	pSys->relativeParticles = false;//true;
	///
#endif
#ifdef PARTICLE_2
	///*
	//For gravity well demo
	pSys->xrange = 50.0;
	pSys->yrange = 40.0;
	pSys->velx = 0.0;
	pSys->vely = 0.0;
	pSys->velxrange = 400.0;
	pSys->velyrange = 400.0;
	//pSys->gravityConstant = false;
	pSys->life = 2.0;
	pSys->liferange = 1.0;
	pSys->maxParticles = 500;
	pSys->relativeParticles = true;
	///
#endif
    
	pSys->gravityDir.x = 0.0;
	pSys->gravityDir.y = -10.0; //-200.0;
	pSys->gravityWell.x = 400.0;
	pSys->gravityWell.y = 300.0;
	pSys->gravityWellConstant = 12.0;
	//pSys->relativeParticles = false;
    pSys->setZOrder(LAYER_MIDDLE);
    //renderList->addChild(pSys); //Used in robot node below
    
    pSys2 = new ParticleSystem("regular1");
    pSys2->maxParticles = 150;
    pSys2->vely = -100.0;
    pSys2->velxrange = 150.0;
    pSys2->velyrange = 150.0;
	pSys2->setPosition(400, 300);
    pSys2->xrange = 50.0;
    pSys2->yrange = 50.0;
	pSys2->gravityDir.y = 180.0; //120.0;
	pSys2->life = 2.0;
	pSys2->systemLife = 6.0;
	pSys2->removeAfterLifeSpan = true;
    pSys2->Init();
    pSys2->setZOrder(LAYER_MIDDLE);
    renderList->addChild(pSys2);
	//pSys2 = NULL; //Clear pointer since it will be removed after its lifespan
    
	//SDL_Surface* scale = load_image("the_mountains_night.PNG", false);

	*/

	/*
    Sprite* sprite = new Sprite("the_mountains_night.PNG", false);
	//resourceManager.SetImage("the_mountains_night.PNG-stretched", zoom
	sprite->getIdComp()->addTag("bg");
	sprite->getIdComp()->addTag("sprite");
	log_info(sprite->getIdComp()->hasTag("bg") ? "Has bg." : "Doesn't have bg.");
    sprite->setZOrder(LAYER_MIDDLE);
    renderList->addChild(sprite);

	//Test of tags and findChildrenWithTag function
	std::vector<RenderTarget*> *foundNodes = renderList->findChildrenWithTag("bg");
	if (foundNodes->size() > 0)
	{
		std::stringstream s;
		s << "Found " << foundNodes->size() << " children with tag bg" << std::endl;

		for (std::vector<RenderTarget*>::iterator iter = foundNodes->begin(); iter != foundNodes->end(); ++iter)
		{
			RenderTarget *node = *iter;
			if (node != NULL)
			{
				s << "Found child id: " << node->getIdComp()->id;
			}
		}
		log_info(s.str());
	}
	*/

 
	/*
	DudeTemplate templates[2];
	templates[0].baseResourceName = "Robot Man.png";
	templates[0].stats.setAll(2, 1, 10, 1);
	templates[1].baseResourceName = "Catomic Juggernaut.png";
	templates[1].stats.setAll(4, 2, 30, 1);


	//Add robots to each side of the battle
	int i = 0;
	while (i < 10)
	{
		int templateId = i > 1 ? 0 : 1;
		Dude* dude = new Dude(*battle, i % 2 == 0 ? PLAYER_LEFT : PLAYER_RIGHT, templates[templateId]);
		if (!battle->AddDude(dude))
			break;
		i++;
	}
	*/
    
	/*
	while (battle->allDudes.size() > 0)
	{
		Dude *dude = battle->allDudes.size() > 0 ? battle->allDudes.at(0) : NULL ;
		if (dude != NULL)
		{
			battle->RemoveDude(dude);
		}
	}
	*/

	//addSceneToFront(renderList, "render");

    /*
	Sprite *sprite = new Sprite("lvl_idea_the_cave.PNG", false);
    
	addSceneToFront(sprite, "menu"); //engine takes care of memory mgmt
    
	RenderTarget* scene = findSceneById("menu");
	if (scene != NULL)
	{
		removeScene(scene);
		scene->setZOrder(getSceneStack()->getZOrder() - 100.0);
		addChild(scene);
	}
    
	//Test of tags feature and finding children with one
	//Find nodes that are sprites
	foundNodes = findChildrenWithTag("sprite");
	if (foundNodes->size() > 0)
	{
		std::stringstream s;
		s << "Found " << foundNodes->size() << " sprite nodes." << std::endl;

		for (std::vector<RenderTarget*>::iterator iter = foundNodes->begin(); iter != foundNodes->end(); ++iter)
		{
			RenderTarget *node = *iter;
			if (node != NULL)
			{
				s << "Found sprite id: " << node->getIdComp()->id << std::endl << "  ResourceName: " << ((Sprite*)node)->getResource() << std::endl;
			}
		}
		//log_info(s.str());
	}
	*/

	//Create a battle scene and start it going
	BattleScene* battle = new BattleScene("battle_scene");
	//battle->SetArena("lvl_idea_the_cave.PNG");
	//battle->SetArena("the_mountains_night.PNG");
	battle->SetArena("TestBG.png", "TestFG.png");

	//Add battle scene to engine at front
	addSceneToFront(battle);

	//Create players and teams
	Player left(PLAYER_LEFT), right(PLAYER_RIGHT);
	Team leftTeam, rightTeam;

	//Fill in teams for now
	leftTeam.AddTemplate("Robot Man.png",				4, 7, 4, 4, 1, 5);
	leftTeam.AddTemplate("Ed.png",						3, 5, 4, 4, 1, 2);
	leftTeam.AddTemplate("Face Hands Monster.png",		5, 3, 5, 7, 1, 3);
	leftTeam.AddTemplate("Gelatinous Snartle Plop.png", 4, 6, 5, 7, 1, 4);

	rightTeam.AddTemplate("Giant Eye.png",				4, 3, 6, 3, 1, 5);
	rightTeam.AddTemplate("Imp.png",					4, 8, 5, 4, 1, 4);
	rightTeam.AddTemplate("Skrugg.png",					7, 3, 6, 6, 1, 6);
	rightTeam.AddTemplate("Tormented Soul.png",			2, 6, 7, 5, 1, 5);
	//rightTeam.AddTemplate("Catomic Juggernaut.png", );

	//Load teams into players
	left.LoadTeam(leftTeam);
	right.LoadTeam(rightTeam);

	//Create picker trays for each player and add to engine
	PickerTray *leftTray, *rightTray;
	leftTray = new PickerTray(left);
	rightTray = new PickerTray(right);
	addSceneToFront(leftTray);
	addSceneToFront(rightTray);

	SDL_initFramerate(&fpsManager);
	SDL_setFramerate(&fpsManager, PD_FRAMERATE_CAP);

	
	Mix_Music* music = resourceManager.GetMusic("Fight 1.ogg");
	if (music != NULL)
	{
		int err = Mix_PlayMusic(music, 0);
		if (err == -1)
		{
			std::stringstream logLine;
			logLine << "Error playing music: " << Mix_GetError() << std::endl;
			log_info(logLine.str());
		}
	}
	
    
    while ( running )
	{
		while ( SDL_PollEvent( &event ) )
		{
			//Use event system to handle each event in the queue
			DispatchEvent(this, &event);
		}
        
		//Cap framerate and get back elapsed time in seconds
		double deltaSeconds = SDL_framerateDelay(&fpsManager) / 1000.0;

		//Cap at 200ms frametime, otherwise probably debugging
		deltaSeconds = deltaSeconds > 0.2 ? 0.2 : deltaSeconds;
        
		//Update everything
		Update(deltaSeconds * speed);
        
		//Render everything
        Render(screen);

		//Count frame for framerate calculation
		frame++;
	}
    
    //Shutdown();
        
    return 0;
}

void GameEngine::staticHeartBeat(void* param, Timer* timer)
{
	GameEngine::getInstance()->HeartBeat(timer);
}

void GameEngine::HeartBeat(Timer* timer)
{
	log_info("Heartbeat");

	//Update fps window title
	std::stringstream timeCaption;
	timeCaption << winName << " - " << (speed * frame / (frameTimer->get_time())) << " FPS (capped @ " << PD_FRAMERATE_CAP << ")";
	SDL_WM_SetCaption( timeCaption.str().c_str(), NULL );
	frame = 0;
	frameTimer->start(); //start back at zero for next measurement

	//Give battlescene the heartbeat event to respond to
	BattleScene *battleScene = (BattleScene*)findChildWithId("battle_scene");
	if (battleScene != NULL)
	{
		battleScene->Heartbeat();
	}

	if (autoPick)
	{
		PickerTray* pickers[] = { dynamic_cast<PickerTray*>(findChildWithId("picker_left")),
								dynamic_cast<PickerTray*>(findChildWithId("picker_right")) };
		//go through both pickers and click any buttons that are enabled
		for (int j = 0; j < 2; j++)
		{
			if (pickers[j] != NULL)
			{
				std::vector<RenderTarget*> *list = pickers[j]->findChildrenWithTag("button");
				for (int i = 0; i < list->size(); i++)
				{
					Button *button = dynamic_cast<Button*>(list->at(i));
					if (button != NULL)
					{
						if (button->GetEnabled())
						{
							Sint16 x, y;
							button->getScreenCoords(x, y);
							button->OnLButtonUp(x + 1, y + 1);
						}
					}
				}
			}
		}
	}
}

void GameEngine::addTimer(Timer* timer)
{
	//Is it already there?
	for (std::vector<Timer*>::iterator iter = activeTimers.begin(); iter != activeTimers.end(); ++iter)
	{
		if (*iter == timer) //If found, leave where it is and return
			return;
	}

	//Add to list
	activeTimers.push_back(timer);
}

void GameEngine::removeTimer(Timer* timer)
{
	std::vector<Timer*>::iterator foundIter = activeTimers.end();

	//Can we find it?
	for (std::vector<Timer*>::iterator iter = activeTimers.begin(); iter != activeTimers.end(); ++iter)
	{
		if (*iter == timer) //If found, leave where it is and return
			foundIter = iter;
	}

	//If found, Remove from list
	if (foundIter != activeTimers.end())
	{
		activeTimers.erase(foundIter); //Remove pointer from list
		delete timer; //Delete timer
	}
}

GameEngine * GameEngine::getInstance() {

	if (gameEngineInstance == NULL)
	{
		gameEngineInstance = new GameEngine();
	}

	return gameEngineInstance;
}

ResourceManager* GameEngine::getResources()
{
	return &GameEngine::getInstance()->resourceManager;
}

SDL_Surface* GameEngine::getScreen()
{
	return screen;
}

void GameEngine::setScreen(SDL_Surface* screen)
{
	this->screen = screen;
}

RenderTarget* GameEngine::getSceneStack()
{
	return renderChildren;
}

void GameEngine::addSceneToFront(RenderTarget* scene)
{
	addSceneToFront(scene, "");
}

void GameEngine::addSceneToFront(RenderTarget* scene, std::string id)
{
	double nextZ = 0.0;

	if (scene == NULL)
		return;

	if (getSceneStack() != NULL)
		nextZ = getSceneStack()->getLast()->getZOrder() + 100.0;
	scene->setZOrder(nextZ);
	if (id.length() != 0)
		scene->getIdComp()->id = id;
	addChild(scene);
}

void GameEngine::removeScene(RenderTarget* scene)
{
	removeChild(scene);
}

RenderTarget* GameEngine::findSceneById(std::string id)
{
	RenderTarget* node = renderChildren;

	if (node == NULL)
		return NULL;

	while (node != NULL)
	{
		if (node->getIdComp()->id.compare(id) == 0)
			return node;
		node = node->getNext();
	}
	return NULL;
}

GameEngine::GameEngine() : RenderTarget("game_engine")
{
	screen = NULL;
	speed = 1.0;
	log_info("GameEngine()");
	running = true;
	fullscreen = false;
	paused = false;
	sfxVolume = 4;
	musicVolume = 4;
	autoPick = false;

	//Register self as global game engine, but take care of old one if any
	if (gameEngineInstance == this)
		return;
	if (gameEngineInstance != NULL)
		delete gameEngineInstance;
	gameEngineInstance = this;
}

void GameEngine::musicFinished()
{
	std::string nextSong = "Fight 1.ogg";
	int song = rand() % 4;
	if (song == 1)
		nextSong = "Fight 3 Orchestra Version.ogg";
	else if (song == 2)
		nextSong = "Fight 3_1.ogg";
	else if (song == 3)
		nextSong = "Fight 4 edit.ogg";

	Mix_Music *music = GameEngine::getResources()->GetMusic(nextSong);
	int err = Mix_PlayMusic(music, 0);
	if (err == -1)
	{
		std::stringstream logLine;
		logLine << "Error playing music: " << Mix_GetError() << std::endl;
		log_info(logLine.str());
	}
}

//Initialize all systems
int GameEngine::Init()
{
	//Set the window caption
    SDL_WM_SetCaption( winName.c_str(), NULL );
    
	screen = SDL_SetVideoMode(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, SDL_SWSURFACE);
    if (screen == NULL)
    {
        log_error("Couldn't initialize screen with default settings");
        return 1;
    }

	Mix_Volume(-1, sfxVolume);
	Mix_VolumeMusic(musicVolume);
	Mix_AllocateChannels(NUMBER_AUDIO_CHANNELS);
	{
		std::stringstream logLine;
		logLine << "Number of audio channels for mixing: " << Mix_AllocateChannels(-1);
		log_info(logLine.str());
	}
	
	Mix_HookMusicFinished(GameEngine::musicFinished);
    
    //Reset frame number
    frame = 0;
    
	//Seed random numbers
	srand(SDL_GetTicks());

	//Setup timer for heartbeat event
	Timer* heartbeat = new Timer(0.3);
	heartbeat->idComponent.id = "HeartBeatTimer";
	heartbeat->set_callback(&GameEngine::staticHeartBeat, NULL);
	heartbeat->set_repeat(true);
	heartbeat->start();
	addTimer(heartbeat);
	//Setup timer to track fps
	frameTimer = new Timer(PD_TIMER_INFINITE);
	frameTimer->start();
	frameTimer->idComponent.id = "frame_timer";
	addTimer(frameTimer);

    return 0;
}

//Shutdown all systems
void GameEngine::Shutdown()
{

}

GameEngine::~GameEngine()
{
	log_info("~GameEngine()");

	//clear active timers and delete them
	std::stringstream s;
	s << "ActiveTimer count: " << activeTimers.size();
	log_info(s.str());
	for (std::vector<Timer*>::iterator iter = activeTimers.begin(); iter != activeTimers.end(); )
	{
		Timer* timer = *iter;

		log_info(std::string("removing timer ") + timer->idComponent.id);
		iter = activeTimers.erase(iter); //Sets iter to next element and removes this one
		delete timer;
	}
}

void GameEngine::Update(double timeDelta)
{
	if (paused)
		return;

	for (std::vector<Timer*>::iterator iter = activeTimers.begin(); iter != activeTimers.end(); )
	{
		Timer* timer = *iter;
		timer->update(timeDelta);

		//Remove finished timers
		if (timer->is_inactive())
		{
			log_info(std::string("removing timer ") + timer->idComponent.id);
			iter = activeTimers.erase(iter); //Sets iter to next element and removes this one
			delete timer;
		}
		//otherwise, normal continue loop
		else
		{
			++iter;
		}
	}

	RenderTarget::Update(timeDelta);
}

void GameEngine::Render(SDL_Surface* screen)
{
	//Clear screen buffer
	SDL_FillRect(screen, NULL, 0);
	//Render children
	RenderTarget::Render(screen);

	//Update whole screen
	SDL_Flip(screen);
}

//Send the given event to the target node and its siblings and children
bool GameEngine::DispatchEvent(RenderTarget *node, SDL_Event* Event)
{
	if (node == NULL)
		return false;

	//Check if it received events
	EventTarget *target = dynamic_cast<EventTarget*>(node);
	if (target != NULL)
	{
		//Send this event to target, it returns whether it handled this event
		bool result = target->OnEvent(Event);
		if (result) //If it was handled, return true and stop processing
			return true;
	}

	//Don't go further if paused
	if (node == this && paused == true)
		return false;

	//Get list of all children, go through from front to back
	std::vector<RenderTarget*> reverseChildren;
	//Pointer to step through children of this node
	RenderTarget* cNode = node->getChildren();

	while (cNode != NULL)
	{
		reverseChildren.push_back(cNode);
		cNode = cNode->getNext(); //Get next child
	}

	//Return true if one of the children handled event
	while (reverseChildren.size() > 0)
	{
		cNode = reverseChildren.back();
		reverseChildren.pop_back();

		//If event was handled, return true
		if (DispatchEvent(cNode, Event))
			return true;
	}

	return false;
}

bool GameEngine::OnExit()
{
	running = false;
	return false;
}

bool GameEngine::OnKeyDown(SDLKey sym, SDLMod mod, Uint16 unicode)
{
	if (sym == SDLK_ESCAPE) //Escape quits
	{
		running = false;
		return true;
	}
	/* //Doesn't really make sense since the arena sizes are set
	else if (sym == SDLK_F11) //F11 toggles fullscreen on windows only right now
	{
        #ifndef __APPLE__
		fullscreen = !fullscreen; //get new setting
		if (fullscreen)
        {
			screen = SDL_SetVideoMode(1600, 900, SCREEN_BPP, SDL_SWSURFACE | SDL_FULLSCREEN);
        }
		else
        {
			screen = SDL_SetVideoMode(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, SDL_SWSURFACE);
        }
		setScreen(screen);
		return true;
        #endif
	}
	*/
	else if (sym == SDLK_F1)
	{
		autoPick = !autoPick;
	}
	else if (sym == SDLK_F10)
	{
		speed *= 1.25;
		std::stringstream logInfo;
		logInfo << "Speed is now at " << (100.0 * speed) << "%";
		log_info(logInfo.str());
	}
	else if (sym == SDLK_F9)
	{
		speed /= 1.25;
		std::stringstream logInfo;
		logInfo << "Speed is now at " << (100.0 * speed) << "%";
		log_info(logInfo.str());
	}
	else if (sym == SDLK_F12)
	{
		paused = !paused;

		if (paused)
		{
			//Mix_PauseMusic();
			Mix_VolumeMusic(musicVolume / 4);

			Sprite *pausedSprite = new Sprite("paused.png", true);
			pausedSprite->setZOrder(1000);
			pausedSprite->centered = true;
			pausedSprite->setPosition(screen->w / 2, screen->h / 2);
			pausedSprite->getIdComp()->id = "pause_image";
			addChild(pausedSprite);
			
		}
		else
		{
			//Mix_ResumeMusic();
			Mix_VolumeMusic(musicVolume);

			RenderTarget* node = findSceneById("pause_image");
			
			if (node != NULL)
			{
				removeScene(node);
			}
		}

	}
	else if (sym == SDLK_MINUS || sym == SDLK_EQUALS)
	{
		if (sym == SDLK_MINUS && musicVolume > 0)
			musicVolume /= 2;
		else if (sym == SDLK_EQUALS && musicVolume < 128)
		{
			if (musicVolume > 0)
				musicVolume *= 2;
			else
				musicVolume = 1;
		}
		if (musicVolume > 128)
			musicVolume = 128;
		sfxVolume = musicVolume;
		Mix_VolumeMusic(musicVolume);
		Mix_Volume(-1, sfxVolume);
	}
	return false;
}